package top.z.template.parse.ast;

import top.z.template.Context;
import top.z.template.Error;
import top.z.template.Template;
import top.z.template.interpreter.AstInterpreter;
import top.z.template.parse.Ast.Expression;
import top.z.template.parse.Ast.Node;
import top.z.template.parse.Span;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * For 语句
 */
public class For extends Node {

    private final Span       symbol;    // list的index或者map的key
    private final Span       valueName; //
    private final Expression data;      // list或者map
    private final List<Node> body;      // 循环体

    public For(Span span, Span symbol, Span valueName, Expression data, List<Node> body) {
        super(span);
        this.symbol = symbol;
        this.valueName = valueName;
        this.data = data;
        this.body = body;
    }

    public Span getSymbol() {
        return symbol;
    }

    public Span getValueName() {
        return valueName;
    }

    public Expression getData() {
        return data;
    }

    public List<Node> getBody() {
        return body;
    }

    @Override
    public Object evaluate(Template template, Context context, OutputStream out) throws IOException {
        Object data = this.getData().evaluate(template, context, out);
        if (data == null) {
            Error.error("Expected a map or array, got null.", getData().getSpan());
        } else {
            String valueName = getValueName().getText();
            if (data instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) data;
                if (getSymbol() != null) {
                    context.push();
                    String keyName = getSymbol().getText();
                    for (Map.Entry<?, ?> entry : map.entrySet()) {
                        context.setCurrentScope(keyName, entry.getKey());
                        context.setCurrentScope(valueName, entry.getValue());
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                } else {
                    context.push();
                    for (Object value : map.values()) {
                        context.setCurrentScope(valueName, value);
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                }
            } else if (data instanceof Iterable) {
                if (getSymbol() != null) {
                    context.push();
                    String      keyName  = getSymbol().getText();
                    Iterator<?> iterator = ((Iterable<?>) data).iterator();
                    int         index    = 0;
                    while (iterator.hasNext()) {
                        context.setCurrentScope(keyName, index++);
                        context.setCurrentScope(valueName, iterator.next());
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                } else {
                    Iterator<?> iterator = ((Iterable<?>) data).iterator();
                    context.push();
                    while (iterator.hasNext()) {
                        context.setCurrentScope(valueName, iterator.next());
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                }
            } else if (data instanceof Iterator) {
                if (getSymbol() != null) {
                    Error.error("Can not do indexed/keyed for loop on an iterator.", getData().getSpan());
                } else {
                    Iterator<?> iterator = (Iterator<?>) data;
                    context.push();
                    while (iterator.hasNext()) {
                        context.setCurrentScope(valueName, iterator.next());
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                }
            } else if (data.getClass().isArray()) {
                Object[] array = (Object[]) data;
                if (getSymbol() != null) {
                    context.push();
                    String keyName = getSymbol().getText();
                    for (int i = 0, n = array.length; i < n; i++) {
                        context.setCurrentScope(keyName, i);
                        context.setCurrentScope(valueName, array[i]);
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                } else {
                    context.push();
                    for (Object o : array) {
                        context.setCurrentScope(valueName, o);
                        // break, continue, return
                        Object breakOrContinueOrReturn = AstInterpreter.interpretNodeList(getBody(), template, context, out);

                        if (breakOrContinueOrReturn == Break.BREAK_SENTINEL) {
                            break;
                        }
                        if (breakOrContinueOrReturn == Return.RETURN_SENTINEL) {
                            context.pop();
                            return breakOrContinueOrReturn;
                        }
                    }
                    context.pop();
                }
            } else {
                Error.error("Expected a map, an array or an iterable, got " + data, getData().getSpan());
            }
        }
        return null;
    }
}
